/****************************************************************************
** Copyright (C) 2017 ZhouLiQiu(zhouliqiu@126.com).
**
** This file is part of the qtc_cppcheck, It is a plugin for Qt Creator.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
**
****************************************************************************/
#include "worker_check.h"
#include "csettings.h"
#include <QStringList>
#include <QByteArray>
#include <QDir>
#include <QDebug>

#include <projectexplorer/projectnodes.h>
#include <projectexplorer/taskhub.h>
#include "qtc_cppcheckconstants.h"

namespace qtc_cppcheck {
namespace Internal {

Worker_Check::Worker_Check(QObject *parent) : QObject(parent),
    m_process(NULL),
    m_tempFile(NULL),
    m_error_num(0),
    m_warn_num(0),
    m_portability_num(0),
    m_style_num(0),
    m_performance_num(0)
{
    m_process = new QProcess;
    connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(slot_readErrors()));
    connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(slot_readOutput()));
    connect(m_process, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(slot_error(QProcess::ProcessError)));
    connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slot_finished(int, QProcess::ExitStatus)));
    connect(m_process, SIGNAL(started()), this, SLOT(slot_started()));

    m_supportFileType << QLatin1String("cpp") << QLatin1String("cxx") << QLatin1String("cc")
                      << QLatin1String("c") << QLatin1String("c++") << QLatin1String("txx")
                      << QLatin1String("tpp")
                      << QLatin1String("h") << QLatin1String("hh") << QLatin1String("hpp")
                      << QLatin1String("h++") << QLatin1String("hxx");
}

Worker_Check::~Worker_Check()
{
    m_process->waitForFinished(4000);
    if (m_process->state() != QProcess::NotRunning)
    {
        m_process->kill();
        m_process->waitForFinished();
    }
    delete m_process;
    if (m_tempFile != NULL)
    {
        delete m_tempFile;
    }
}

bool Worker_Check::isRunning()
{
    return m_process->state() != QProcess::NotRunning;
}

void Worker_Check::stopCheck()
{
    if (m_process->state() != QProcess::NotRunning)
    {
        m_process->kill();
        m_process->waitForFinished();
    }
}

void Worker_Check::checkNode(const ProjectExplorer::Node *node)
{
    QStringList listFile = getNodeFiles(node);
    checkFiles(listFile);
}

void Worker_Check::checkFiles(QStringList &listFile)
{
    for (int i = (listFile.size() - 1); i >= 0; --i)
    {
        if (!isCheckable(listFile.at(i)))
        {
            listFile.removeAt(i);
        }
    }
    if (listFile.size() == 0 || m_process == NULL)
    {
        return;
    }

    CSettings settings;
    QString exeFileName = settings.getExeFileName();
    QStringList arguments = settings.getCheckArgString();

    if (!exeFileName.endsWith("cppcheck.exe", Qt::CaseInsensitive))
    {
        emit sig_finished(-1, QProcess::CrashExit, 0, 0, 0, 0, 0);
        return;
    }
    if (!QFile::exists(exeFileName))
    {
        emit sig_finished(-1, QProcess::CrashExit, 0, 0, 0, 0, 0);
        return;
    }

    if (m_process->state() != QProcess::NotRunning)
    {
        m_process->kill();
        m_process->waitForFinished();
    }
    if (m_tempFile != NULL)
    {
        delete m_tempFile;
        m_tempFile = NULL;
    }
    m_tempFile = new QTemporaryFile;

    QString strCheckFile = listFile.join(QLatin1String(" "));
    if (strCheckFile.length() > 20000) // too long, use --file-list=
    {
        if (m_tempFile->open())
        {
            QByteArray fileData = listFile.join(QLatin1String("\n")).toLocal8Bit();
            m_tempFile->write(fileData);
            m_tempFile->close();
        }
        arguments << QString("--file-list=%1").arg(m_tempFile->fileName());
    }
    else
    {
        arguments += listFile;
    }
    m_process->start(settings.getExeFileName(), arguments);
}

void Worker_Check::slot_readOutput()
{
    m_process->setReadChannel(QProcess::StandardOutput);

    while (!m_process->atEnd())
    {
        QByteArray rawLine = m_process->readLine();
        QString line = QString::fromUtf8(rawLine).trimmed();
        if (line.isEmpty())
        {
            continue;
        }
        //qDebug() << line;
        const QString progressSample = QLatin1String("% done");
        if (line.endsWith(progressSample))
        {
            int percentEndIndex = line.length() - progressSample.length();
            int percentStartIndex = line.lastIndexOf(QLatin1String(" "), percentEndIndex);
            int done = line.mid(percentStartIndex, percentEndIndex - percentStartIndex).toInt();
            emit sig_check_progress(done);
        }
    }
}

void Worker_Check::slot_readErrors()
{
    m_process->setReadChannel(QProcess::StandardError);

    while (!m_process->atEnd())
    {
        QByteArray rawLine = m_process->readLine();
        QString line = QString::fromUtf8(rawLine).trimmed();
        if (line.isEmpty())
        {
            continue;
        }
        QStringList details = line.split(QLatin1Char(','));
        if (details.size() < 5)
        {
            continue;
        }
        QString fileName = QDir::fromNativeSeparators(details.at(0));
        int lineNumber = details.at(1).toInt();
        QString type = details.at(2);
        QString id = details.at(3);
        QString description = line.mid(line.indexOf(details.at(4)));
        //qDebug() << "***" << type << id << description << "***";

        QFileInfo info(fileName);
        if (!info.exists())  // Not points to file.
        {
            return;
        }
        Utils::FilePath file = Utils::FilePath::fromFileInfo(info);
        QString fullDescription = QString("%1 (%2) (%3) %4")
                                  .arg(QString(qtc_cppcheck::Constants::TASK_CATEGORY_NAME))
                                  .arg(type)
                                  .arg(id)
                                  .arg(description);

        ProjectExplorer::Task::TaskType taskType = ProjectExplorer::Task::Warning;
        if (type == "error")
        {
            m_error_num++;
            taskType = ProjectExplorer::Task::Error;
        }
        else if (type == "warning")
        {
            m_warn_num++;
        }
        else if (type == "style")
        {
            m_style_num++;
        }
        else if (type == "portability")
        {
            m_portability_num++;
        }
        else
        {
            m_performance_num++;
        }
        ProjectExplorer::Task task(taskType, fullDescription, file, lineNumber, qtc_cppcheck::Constants::TASK_CATEGORY_ID);
        ProjectExplorer::TaskHub::addTask(task);
        ProjectExplorer::TaskHub::requestPopup();
    }
}

void Worker_Check::slot_error(QProcess::ProcessError error)
{
    if (error == QProcess::FailedToStart)
    {
        slot_finished(-1, QProcess::CrashExit);
    }
}

void Worker_Check::slot_finished(int exitCode, QProcess::ExitStatus exitStatus)
{
    emit sig_finished(exitCode, exitStatus, m_error_num, m_warn_num, m_style_num, m_portability_num, m_performance_num);
}

void Worker_Check::slot_started()
{
    ProjectExplorer::TaskHub::clearTasks();
    m_error_num = 0;
    m_warn_num = 0;
    m_portability_num = 0;
    m_performance_num = 0;
    m_style_num = 0;
}

bool Worker_Check::isCheckable(QString fileName)
{
    QFileInfo info(fileName);
    QString suffix = info.suffix();
    return m_supportFileType.contains(suffix, Qt::CaseInsensitive);
}

QStringList Worker_Check::getNodeFiles(const ProjectExplorer::Node *node)
{
    QStringList files;

    if (node->isGenerated())
    {
        return files;
    }

    if (node->isFolderNodeType() || node->isProjectNodeType() || node->isVirtualFolderType())
    {
        const ProjectExplorer::FolderNode* folder =NULL;
        if (const ProjectExplorer::ContainerNode *container = node->asContainerNode())
        {
            folder = container->rootProjectNode();
        }
        if (!folder)
        {
            folder = node->asFolderNode();
        }
        if (folder)
        {
            foreach(const ProjectExplorer::FolderNode *subfolder, folder->folderNodes())
            {
                files += getNodeFiles(subfolder);
            }
            foreach(const ProjectExplorer::FileNode *file, folder->fileNodes())
            {
                files += getNodeFiles(file);
            }
        }
    }
    else
    {
        const ProjectExplorer::FileNode* file = node->asFileNode();
        if (file)
        {
            files << file->filePath().toString();
        }
    }
    return files;
}

} // namespace Internal
} // namespace qtc_cppcheck
